#![allow(dead_code, unused)]

use std::{collections::{HashMap}, ffi::*, mem::offset_of, ptr::{copy, copy_nonoverlapping, null, null_mut}, sync::{Arc, Mutex}};
use getset::{CloneGetters, CopyGetters, Getters, MutGetters, Setters, WithSetters};
use ash::vk::{self, Handle, VertexInputBindingDescription, VertexInputAttributeDescription};

use crate::engine::{njm_buffer::{NjmBuffer, DEFAULT_MAP_OFFSET, DEFAULT_MAP_SIZE, DEFAULT_MIN_OFFSET_ALIGN}, njm_device::NjmDevice};
use asset_importer_rs::AssetImporter;

#[derive(PartialEq, Getters, Setters, WithSetters, MutGetters, CopyGetters, CloneGetters)]
pub struct Vertex {

    #[getset(get = "pub")]
    position: cgmath::Vector3<f32>,

    #[getset(get = "pub")]
    color: cgmath::Vector3<f32>,

    #[getset(get = "pub")]
    normal: cgmath::Vector3<f32>,

    #[getset(get = "pub")]
    uv: cgmath::Vector2<f32>,
}

impl Default for Vertex {
    fn default() -> Self {
        return Self {
            position: [0.0, 0.0, 0.0].into(),
            color: [0.0, 0.0, 0.0].into(),
            normal: [0.0, 0.0, 0.0].into(),
            uv: [0.0, 0.0].into(),
        };
    }
}

impl Vertex {
    pub fn get_binding_descriptions() -> Vec<VertexInputBindingDescription> {
        let bindings = vec![
            vk::VertexInputBindingDescription {
                binding: 0,
                stride: size_of::<Vertex>() as u32,
                input_rate: vk::VertexInputRate::VERTEX,
            },
        ];
        return bindings;
    }

    pub fn get_attribute_descriptions() -> Vec<VertexInputAttributeDescription> {        
        let attrs = vec![
            vk::VertexInputAttributeDescription {
                location: 0,
                binding: 0,
                format: vk::Format::R32G32B32_SFLOAT,
                offset: offset_of!(Vertex, position) as u32,
            },
            vk::VertexInputAttributeDescription {
                location: 1,
                binding: 0,
                format: vk::Format::R32G32B32_SFLOAT,
                offset: offset_of!(Vertex, color) as u32,
            },
            vk::VertexInputAttributeDescription {
                location: 2,
                binding: 0,
                format: vk::Format::R32G32B32_SFLOAT,
                offset: offset_of!(Vertex, normal) as u32,
            },
            vk::VertexInputAttributeDescription {
                location: 3,
                binding: 0,
                format: vk::Format::R32G32_SFLOAT,
                offset: offset_of!(Vertex, uv) as u32,
            },
        ];
        return attrs;
    }
}

#[derive(Default)]
pub struct ModelBuilder {
    vertices: Vec<Vertex>,
    indices: Vec<u32>,
}

impl ModelBuilder {
    pub fn load_model(&mut self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
        let scene = AssetImporter::from_file(path)?;
        
        todo!()
    }
}

pub type id_t = String;
pub type Map = HashMap<id_t, Arc<NjmModel>>;

#[derive(Getters, Setters, WithSetters, MutGetters, CopyGetters, CloneGetters)]
pub struct NjmModel {
    dev: Arc<Mutex<NjmDevice>>,

    #[getset(get = "pub")]
    id: Option<id_t>,

    #[getset(get = "pub")]
    name: Option<String>,

    #[getset(get = "pub")]
    vert_buf: Box<NjmBuffer>,

    #[getset(get = "pub")]
    vert_cnt: u32,

    #[getset(get = "pub")]
    idx_buf: Option<Box<NjmBuffer>>,

    #[getset(get = "pub")]
    idx_cnt: u32, 
}

impl NjmModel {
    
    pub fn new(dev: Arc<Mutex<NjmDevice>>, builder: &ModelBuilder) -> Self {
        let vcnt = builder.vertices.len();
        let icnt = builder.indices.len();
        assert!(vcnt > 0);
        let vbuf = Self::create_vertex_buffers(dev.clone(), &builder.vertices);
        let ibuf = Self::create_index_buffers(dev.clone(), &builder.indices);

        return Self {
            dev: dev.clone(),
            id: None,
            name: None,
            vert_buf: vbuf,
            vert_cnt: vcnt as u32,
            idx_buf: ibuf,
            idx_cnt: icnt as u32,
        };
    }

    pub fn create_vertex_buffers(dev: Arc<Mutex<NjmDevice>>, vertices: &Vec<Vertex>) -> Box<NjmBuffer> {
        let cnt = vertices.len();
        let vert_size = size_of::<Vertex>();
        let buf_size = cnt * vert_size;

        let mut stage_buf = NjmBuffer::new(
            dev.clone(), vert_size as u64, cnt as u32, 
            vk::BufferUsageFlags::TRANSFER_SRC, 
            vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, DEFAULT_MIN_OFFSET_ALIGN);
        stage_buf.map(DEFAULT_MAP_SIZE, DEFAULT_MAP_OFFSET);
        stage_buf.write_to_buffer(vertices.as_ptr() as *const c_void, DEFAULT_MAP_SIZE, DEFAULT_MAP_OFFSET);

        let vert_buf = Box::new(NjmBuffer::new(
            dev.clone(), vert_size as u64, cnt as u32, 
            vk::BufferUsageFlags::VERTEX_BUFFER | vk::BufferUsageFlags::TRANSFER_DST, 
            vk::MemoryPropertyFlags::DEVICE_LOCAL, DEFAULT_MIN_OFFSET_ALIGN));
        dev.lock().unwrap().copy_buffer(*stage_buf.buf(), *vert_buf.buf(), buf_size as vk::DeviceSize);
        return vert_buf;
    }

    pub fn create_index_buffers(dev: Arc<Mutex<NjmDevice>>, indices: &Vec<u32>) -> Option<Box<NjmBuffer>> {
        let cnt = indices.len();
        if cnt == 0 {return None;}
        let index_size = size_of::<u32>();
        let buf_size = cnt * index_size;

        let mut stage_buf = NjmBuffer::new(
            dev.clone(), index_size as u64, cnt as u32, 
            vk::BufferUsageFlags::TRANSFER_SRC, 
            vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, DEFAULT_MIN_OFFSET_ALIGN);
        stage_buf.map(DEFAULT_MAP_SIZE, DEFAULT_MAP_OFFSET);
        stage_buf.write_to_buffer(indices.as_ptr() as *const c_void, DEFAULT_MAP_SIZE, DEFAULT_MAP_OFFSET);

        let index_buf = Box::new(NjmBuffer::new(
            dev.clone(), index_size as u64, cnt as u32, 
            vk::BufferUsageFlags::INDEX_BUFFER | vk::BufferUsageFlags::TRANSFER_DST, 
            vk::MemoryPropertyFlags::DEVICE_LOCAL, DEFAULT_MIN_OFFSET_ALIGN));
        dev.lock().unwrap().copy_buffer(*stage_buf.buf(), *index_buf.buf(), buf_size as vk::DeviceSize);
        return Some(index_buf);
    }

    pub fn create_model_from_file(dev: Arc<Mutex<NjmDevice>>, path: &str) -> Box<NjmModel> {
        let mut builder = ModelBuilder::default();
        let r = builder.load_model(path).unwrap();
        return Box::new(NjmModel::new(dev, &builder));
    }

    // public api
    pub fn bind(&self, cmdbuf: vk::CommandBuffer) {
        let njm_dev = self.dev.lock().unwrap();
        unsafe { njm_dev.device().cmd_bind_vertex_buffers(
                cmdbuf, 0, &[*self.vert_buf.buf()], &[0]) };
        if let Some(i_buf) = &self.idx_buf {
            unsafe { njm_dev.device().cmd_bind_index_buffer(
                cmdbuf, *i_buf.buf(), 0, vk::IndexType::UINT32) };
        }
    }

    pub fn draw(&self, cmdbuf: vk::CommandBuffer) {
        let njm_dev = self.dev.lock().unwrap();
        if self.idx_buf.is_some() {
            unsafe { njm_dev.device().cmd_draw_indexed(cmdbuf, self.idx_cnt, 1, 0, 0, 0) };
        } else {
            unsafe { njm_dev.device().cmd_draw(cmdbuf, self.vert_cnt, 1, 0, 0) };
        }
    }

    // model building helper
    pub fn create_cube_model(dev: Arc<Mutex<NjmDevice>>, offset: cgmath::Vector3<f32>) -> Box<NjmModel> {
        let mut builder: ModelBuilder = ModelBuilder::default();
        let verts: Vec<Vertex> = vec![
            Vertex { position: [-0.5, -0.5, 0.5].into(),    color: [1.0, 0.0, 0.0].into(), ..Default::default()}, // 红色
            Vertex { position: [0.5, -0.5, 0.5 ].into(),    color: [0.0, 1.0, 0.0].into(), ..Default::default()},  // 绿色
            Vertex { position: [-0.5, 0.5, 0.5].into(),     color: [0.0, 0.0, 1.0].into(), ..Default::default()},  // 蓝色
            Vertex { position: [0.5, 0.5, 0.5].into(),      color: [1.0, 1.0, 0.0].into(), ..Default::default()},   // 黄色
            Vertex { position: [-0.5, -0.5, -0.5].into(),   color: [1.0, 0.0, 1.0].into(), ..Default::default()}, // 紫色
            Vertex { position: [0.5, -0.5, -0.5].into(),    color: [0.0, 1.0, 1.0].into(), ..Default::default()},  // 青色
            Vertex { position: [-0.5, 0.5, -0.5].into(),    color: [1.0, 1.0, 1.0].into(), ..Default::default()},  // 白色
            Vertex { position: [0.5, 0.5, -0.5].into(),     color: [0.0, 0.0, 0.0].into(), ..Default::default()}    // 黑色
        ];
        let idxs: Vec<u32> = vec![
            0, 1, 2, 2, 1, 3, // 前面
            4, 5, 6, 6, 5, 7, // 后面
            0, 2, 4, 4, 2, 6, // 左面
            1, 3, 5, 5, 3, 7, // 右面
            0, 1, 4, 4, 1, 5, // 底面
            2, 3, 6, 6, 3, 7  // 顶面
        ];
        builder.vertices = verts;
        builder.indices = idxs;

        for v in &mut builder.vertices {
            v.position += offset;
        }
        let mut model = NjmModel::new(dev, &builder);
        let name = "cube";
        model.name = Some(name.to_string());
        model.id = Some(name.to_string());
        return Box::new(model);
    }

}

impl Drop for NjmModel {
    fn drop(&mut self) {
    }
}